Перейти к содержанию

Autodesk Inventor API. Первые шаги/Взаимодействие с пользователем

Материал из Викиучебника — открытых книг для открытого мира

Взаимодействие с пользователем

[править]

Важным элементом многих программ является организация взаимодействия с конечным пользователем. Доступны различные виды взаимодействия. Во-первых, взаимодействие посредством ваших собственных диалоговых окон. Например, в приложении для задач ЧПУ вполне уместен диалог, в котором пользователь задает параметры инструмента. Такой диалог создается и управляется с помощью любых штатных средств организации диалоговых окон в используемом вами языке программирования. Этот способ отличается тем, что не использует функционал Inventor API, поэтому подробно рассматриваться не будет. Гораздо больший интерес представляют способы взаимодействия с пользователем. специфичные для Autodesk Inventor, — выделение объектов, команды мыши, вывод сообщений и подсвечивание графических элементов.


Выделение объектов

[править]

На практике набольшую роль играет выделение объектов. Для этой задачи Autodesk Inventor поддерживает две технологии: множество выделенных объектов SelectSet и интерактивное выделение InteractionEvents. Каждый из методов по-своему хорош, и во многих программах широко применяются оба.

Набор выделенных объектов (SelectSet)

[править]

Набор выделенных объектов (select set) является самым простым, но вместе с тем и наименее гибким методом выделения. Набор представляет собой множество выделенных пользователем с помощью команды Select объектов. Команда Select в Autodesk Inventor активна, когда не выполняются иные команды. Пользователь может явным образом инициировать команду Select с помощью одноименной кнопки в панели инструментов. С помощью выпадающего подменю пользователю доступны фильтры, управляющие приоритетами отбора объектов.

Transactions_1


Команда Select выполняется совершенно независимо от API. Программист может посредством API лишь узнать текущее состояние множества выделенных объектов — что к этому моменту выделил пользователь.

Команды, использующие выделенные объекты, функционируют по схеме «объект — действие», т.е. пользователь сначала выделяет те или иные объекты, а затем инициирует команду, которая желаемую команду. Типичные примеры — команды Delete и Fillet.

С точки зрения программиста использовать набор выделенных объектов очень просто, т.к. от него не требуется ни малейших усилий для организации самого процесса выделения. Пользователь сам решает, что необходимо выделить, чтобы продолжить работу. Ваша программа лишь видит конечный результат процесса выделения объектов, может проверть их на пригодность и, если все нормально, выполнить с объектами нужные действия.

Объект SelectSet доступен во всех документах через свойство SelectSet и действует как объект–контейнер для всех выделенных командой Select объектов. Ниже приведен пример, который иллюстрирует процедуру проверки, одна ли грань выделена, и если одна, то отображает ее площадь.

Public Sub ShowSurfaceArea()

    ' Ссылка на набор выделенных объектов активного документа	
    Dim oSelectSet As SelectSet
    Set oSelectSet = ThisApplication.ActiveDocument.SelectSet
    
    ' Убедимся, что выделена только одна грань
    If oSelectSet.Count = 1 Then
        
        ' Убедимся, что выделена именно грань
        If TypeOf oSelectSet.Item(1) Is Face Then
            ' Получим ссылку на выделенную грань
            Dim oFace As Face
            Set oFace = oSelectSet.Item(1)
            
            ' Выведем площадь выделенной грани 
            MsgBox "Площадь грани: " & oFace.Evaluator.Area & " см^2"
            Exit Sub
        Else
            MsgBox "Вы должны выделить грань."
            Exit Sub
        End If
    Else
        MsgBox "Вы должны выделить только одну грань."
        Exit Sub
    End If
End Sub

Помимо доступа к выделенным объектам SelectSet поддерживает методы добавления объектов в набор и удаления их оттуда.

SelectSet является однозначно лучшим выбором для программ, работающих по схеме объект – действие. Он часто применяется еще и благодаря чрезвычайной простоте реализации. Тем не менее, упрощение на стороне программиста оборачивается усложнением интерфейса конечного пользователя. Например, в предыдущем примере использование технологии набора выделенных объектов не обязательно является самым удачным решением, чтобы получить от пользователя выделенную грань, поскольку уже в процессе выделения он должен точно представлять, что именно потребуется следующей команде. В отсутствие контроля пользователь вполне может выделить не те объекты. С помощью интерактивного выделения, которое обсуждается ниже, вы можете управлять самим процессом выделения с тем, чтобы только нужные типы объектов могли быть выделены.

И хотя наборы выделенных объектов во многих случаях не являются оптимальным решением, они полезны во многих случаях, а легкость применения нередко компенсирует многие их недостатки. В частности, SelectSet чрезвычайно полезен при написании небольших тестовых программ, которым на входе требуется объект конкретного типа.

Интерактивное выделение (InteractionEvents)

[править]

Интерактивное выделение является мощным инструментом взаимодействия с пользователем благодаря возможности вашего участия в самом процессе выделения объектов. С этой целью поддерживается целый ряд событий, благодаря которым вы можете знать о том, что происходит, и можете повлиять на то, что видит пользователь. Если приглядеться к работе команд самого Инвентора, можно заметить, что для каждой из них характерен собственный стиль поведения, помогающий пользователю выделять лишь те объекты, которые имеют смысл в контексте выполняемой задачи. Например, при вызове команды «Fillet» (Сопряжение) вы можете выделять ребра модели, но не грани, рабочую геометрию и проч. Кроме того, автоматически выделяются и другие ребра, если они связаны с уже выделенным ребром зависимостью касательности. Здесь представлен краткий обзор объектов и событий интерактивного выделения.

Объект InteractionEvents позволяет контролировать процесс выделения объектов в существенно большей степени, чем SelectSet. Объект InteractionEvents поведением напоминает команды самого Инвентора. С началом его работы прерывается текущая команда, и он становится активной командой. На InteractionEvents перенаправляется весь поток событий, связанных со взаимодействием с пользователем. С этого момента вы можете фильтровать этот поток и реагировать на значимые для вашего приложения события. На рисунке ниже приведена часть объектной модели API, относящаяся к объектам InteractionEvents и SelectEvents.

Объект InteractionEvents поддерживает доступ к четырём наборам событий: SelectEvents, MouseEvents, KeyboardEvents и TriadEvents. Для наборов SelectEvents и MouseEvents характерно то, что в каждый конкретный момент времени вы можете получать события только от одного из них, но не от обоих одновременно. Который из них сгенерирует событие, зависит от значения свойства SelectionActive объекта InteractionEvents. Зато набор KeyboardEvents всегда доступен и никакими иными событиями не ограничен.

Объект InteractionEvents поддерживает ряд событий, которые используются для его корректной работы. Например, если пользователь активирует иную команду, генерируется событие OnTerminate, информируя вас о необходимости выполнить предусмотренный комплекс завершающих операций.


Последовательность шагов при использовании интерактивного выделения выглядит следующим образом:

  • Создается объект InteractionEvents
  • Его поведение регулируется заданием соответствующих свойств
  • Устанавливается связь с событиями объекта InteractionEvents
  • Устанавливается связь с событиями объекта SelectEvents
  • Инициируется процесс интерактивного выделения и реакция на поступающие события

Рассмотрим на примере этапы реализации процедуры интерактивного выделения ребер. Пользователю предлагается выделить ребро и, как только выделение состоялось, отображает его длину. Первым делом следует создать объект InteractionEvents вызовом метода CreateInteractionEvents объекта CommandManager. Далее следует инициализировать ряд объектов, связывая их с интересующими нас событиями, и задать различные их свойства, чтобы обеспечить желаемое поведение. InteractionEvents поддерживает события для объектов SelectEvents, MouseEvents и KeyboardEvents.

Как только с помощью событий и свойств разных объектов будет задано желаемое поведение, вызовом метода Start объекта InteractionEvents можно запустить сам процесс выделения. Следует отметить, что старт объекта InteractionEvents, как и активация команд самого Инвентора, прерывает исполнение текущей команды. Справедливо и обратное, старт команды Autodesk Inventor в момент работы InteractionEvents, прерывает исполнение InteractionEvents. Из этого правила есть лишь одно исключение — команды визуализации. Они не прерывают текущую команду, а временно приостанавливают ее выполнение до завершения команды визуализации. Объект InteractionEvents может предоставить информацию, которая необходима для корректной обработки этих ситуаций.


Основная процедура Edge_Length_Test размещается в стандартном модуле VBA. Она вызывает метод Pick объекта clsSelect с фильтром, который ограничивает выделение только подмножеством рёбер модели. Метод Pick возвращает либо ребро, и тогда программа выводит в окно сообщения его длину в миллиметрах, либо Nothing, если пользователь отказался от выбора, нажав, например, клавишу Esc.

Public Sub Edge_Length_Test()

   'Создание нового объекта clsSelect.
   Dim oSelect As New cls_EdgeSelect
   '
   'Вызов метода Pick объекта clsSelect с фильтром,
   'который ограничивает выделение только ребрами.
   Dim oEdge As Edge
   Set oEdge = oSelect.Pick(kPartEdgeFilter)
   
   ' информационное сообщение пользователю
   Dim Msg As String
   
   'Проверка, что ребро выделено.
   If Not oEdge Is Nothing Then
      'Вычисление длины выделенного ребра
      Dim Length As Double
      Length = GetEdgeLength(oEdge)
      MsgBox "Точная длина выделенного ребра: " & vbNewLine _
            & Math.Round(Length * 10, 5) & " мм"
   Else
      MsgBox "Выделение отменено"
   End If

End Sub  '~~~ Edge_Length_Test ~~~

'возвращает длину ребра любого типа (в сантиметрах)
Function GetEdgeLength(ByVal oEdge As Edge) As Double
   
   Dim dLength As Double, dMin As Double, dMax As Double
   
   Call oEdge.Evaluator.GetParamExtents(dMin, dMax)
   Call oEdge.Evaluator.GetLengthAtParam(dMin, dMax, dLength)
   GetEdgeLength = dLength
   
End Function


Приведенный далее код следует разместить в модуле класса cls_EdgeSelect.

Метод Pick инициализирует процесс интерактивного выделения с заданным пользователем фильтром и ожидает завершения, наблюдая за логической переменной bStillSelecting. Затем возвращает вызывающей процедуре либо выделенное пользователем ребро, либо нулевой указатель Nothing в случае отмены операции.

'*************************************************************
'Приведенный ниже код следует поместить
'модуль класса cls_EdgeSelect.
'*************************************************************

' Объявление объектов поддержки событий
Private WithEvents oInteractEvents As InteractionEvents
Private WithEvents oSelectEvents As SelectEvents

' флаг окончания процесса выделения
Private bStillSelecting As Boolean


'Возвращает ссылку на выделенный объект
Public Function Pick(filter As SelectionFilterEnum) As Object
   
   bStillSelecting = True 'Инициализация флага
   
   ' Создание объекта InteractionEvents
   Set oInteractEvents = ThisApplication.CommandManager.CreateInteractionEvents
   
   ' Убедимся, что взаимодействие не подавлено.
   oInteractEvents.InteractionDisabled = False
   
   ' Ссылка на события выделения SelectEvents
   Set oSelectEvents = oInteractEvents.SelectEvents
   
   ' задаем условия фильтрации выделяемых объектов
   oSelectEvents.AddSelectionFilter filter
   
   'Задаем режим выделения только одного объекта
   oSelectEvents.SingleSelectEnabled = True
   'активируем режим отображения подсказок
   ThisApplication.GeneralOptions.ShowCommandPromptTooltips = True
   
   ' Начало работы объекта InteractionEvents
   oInteractEvents.Start
   
   ' Цикл ожидания завершения процесса выделения
   Do While bStillSelecting
      DoEvents
   Loop
'Примечание к циклу ожидания:
'При работе на 64-битной версии Инвентора 
'VBA-вызов DoEvents следует заменить на 
'ThisApplication.UserInterfaceManager.DoEvents. 
'Дело в том, что в 64-битной конфигурации код VBA выполняется 
'НЕ в доном процессе с самим Инвентором, 
'как это происходит в 32-битной конфигурации.

   
   ' Получим ссылку на первый выделенный объект, игнорируя прочие.
   Dim oSelectedEnts As ObjectsEnumerator
   Set oSelectedEnts = oSelectEvents.SelectedEntities
   If oSelectedEnts.Count > 0 Then
       Set Pick = oSelectedEnts.Item(1)
   Else
       Set Pick = Nothing 'выделение отменено (пусто)
   End If
   
   ' Останавливаем процесс выделения InteractionEvents.
   oInteractEvents.Stop
   
   ' Уходя, гасите всех... (Clean up)
   Set oSelectEvents = Nothing
   Set oInteractEvents = Nothing
End Function

Заслуживает небольшого внимания использование события OnPreSelect.

Данное событие генерируется в момент, когда курсор мыши проходит над объектом, выделение которого допускается действующим фильтром, причем событие генерируется ДО подсвечивания объекта. Это дает возможность программе провести дополнительный анализ "кандидата". В данном примере проверяется геометрия ребра. Если ребро не является круглым или линейным, то "кандидат" отбраковывается, аргументу DoHighlight присваивается значение False, что запрещает Инвентору выделять ребро.

Дополнительно используется удобный вариант информационных сообщений посредством переопределения свойства InteractionEvents.StatusBarText, в котором выводятся подсказки и вычисленная "на лету" длина ребра, если оно удовлетворяет требованиям по геометрии. Способ хорош тем, что обеспечивает пользователя контекстно-чувствительной информацией о модели при абсолютном минимуме телодвижений с его стороны.

Private Sub oSelectEvents_OnPreSelect( _
            ByRef PreSelectEntity As Object, _
            ByRef DoHighlight As Boolean, _
            ByRef MorePreSelectEntities As Inventor.ObjectCollection, _
            ByVal SelectionDevice As Inventor.SelectionDeviceEnum, _
            ByVal ModelPosition As Inventor.Point, _
            ByVal ViewPosition As Inventor.Point2d, _
            ByVal View As Inventor.View)

   Dim Msg As String
   
   'благодаря фильтрации выделенным может быть только ребро
   Dim oEdge As Edge
   Set oEdge = PreSelectEntity ' Ссылка на подсвеченное ребро
   
   'реакция в зависимости от типа ребра
   Select Case oEdge.GeometryType
   
      Case CurveTypeEnum.kCircleCurve
         Msg = "Круглое ребро" & vbNewLine & _ _
               "Длина, мм = " & Math.Round(GetEdgeLength(oEdge) * 10, 1)
               
      Case CurveTypeEnum.kLineSegmentCurve
         Msg = "Прямое ребро" & vbNewLine & _
               "Длина, мм = " & Math.Round(GetEdgeLength(oEdge) * 10, 1)
               
      Case Else
         Msg = "Этот тип рёбер не поддерживается"
         ' Не позволяем визуально выделять ребра иных типов
         DoHighlight = False
         
   End Select
   oInteractEvents.StatusBarText = Msg
End Sub

Обработчики событий OnSelect и OnTerminate выставляют признак окончание работы bStillSelecting в значение False, что приводит к завершению цикла ожидания в методе Pick.

Private Sub oSelectEvents_OnSelect( _
            ByVal JustSelectedEntities As Inventor.ObjectsEnumerator, _
            ByVal SelectionDevice As Inventor.SelectionDeviceEnum, _
            ByVal ModelPosition As Inventor.Point, _
            ByVal ViewPosition As Inventor.Point2d, _
            ByVal View As Inventor.View)
   bStillSelecting = False    'флаг завершения процесса выделения
End Sub

Private Sub oInteractEvents_OnTerminate()
   'выставляем флаг завершения процесса выделения
   bStillSelecting = False
   ' Очистка строки подсказки
   oInteractEvents.StatusBarText = ""
End Sub

Private Sub oSelectEvents_OnUnSelect( _
            ByVal UnSelectedEntities As Inventor.ObjectsEnumerator, _
            ByVal SelectionDevice As Inventor.SelectionDeviceEnum, _
            ByVal ModelPosition As Inventor.Point, _
            ByVal ViewPosition As Inventor.Point2d, _
            ByVal View As Inventor.View)
   oInteractEvents.StatusBarText = "Укажите прямое или круглое ребро."
End Sub

Private Sub oSelectEvents_OnStopPreSelect( _
            ByVal ModelPosition As Inventor.Point, _
            ByVal ViewPosition As Inventor.Point2d, _
            ByVal View As Inventor.View)
   oInteractEvents.StatusBarText = "Укажите прямое или круглое ребро."
End Sub

Отмена выполненных действий (TransactionManager)

[править]

Типичные варианты применения транзакций

[править]

Транзакции (Transaction) используются Инвентором для отслеживания выполняемых действий. С точки зрения пользователя, каждая транзакция есть некоторое действие, которое может быть отменено (Undo). API открывает доступ к транзакциям так, чтобы вы могли управлять из своей программы процессом отмены выполняемых действий. Например, если вы разрабатываете процедуру, которая создает полностью определенный зависимостями эскизный элемент, вы, вероятно, захотите, чтобы конечный пользователь воспринимал эту операцию как неделимое действие, отменяемое за один шаг Undo. Без управления транзакциями через API, пользователь в процессе отмены будет иметь дело с последовательностью элементарных шагов (напр., создана линия, создана еще одна линия, создана дуга, наложена зависимость касательности (tangent constraint), создан управляющий размер (dimension constraint) и т.п.). Без вмешательства в последовательность шагов отмены сама возможность отмены становится для конечного пользователя бесполезной, особенно, если учесть конечный размер списка отмены длиной по умолчанию в 10 действий.


Функционал транзакций становится доступным через менеджер транзакций (TransactionManager) объекта Application. TransactionManager поддерживает ряд методов, свойств и событий, что дает вам возможность работать с транзакциями. TransactionManager при первом знакомстве слегка озадачивает — довольно много всего, а применение интуитивно непрозрачно. Изначальный вариант транзакционной системы неотчетливо отражает внутреннюю транзакционную функциональность самого Инвентора. На практике же большая часть этого инструментария не используется, поэтому обсуждение будет сфокусировано на наиболее популярных средствах. Transactions_1


В основе управления транзакциями через API лежит следующая последовательность действий: вы начинаете транзакцию API, вызываете другие функции API, добиваясь желаемого результата, и завершаете транзакцию API. Транзакции, имевшие место между началом и концом транзакции API, скрыты от конечного пользователя. Они еще называются вложенными транзакциями, поскольку ваши промежуточные действия оказываются между началом и завершением транзакции API. Когда конечный пользователь выполняет отмену транзакции API или внешней транзакции, отменяются также и все вложенные в них транзакции. Рассмотрим пример использования транзакций для управления отменой выполненных действий. В примере в активном эскизе создается простой эскизный контур.

Public Sub DrawKeyway()
    ' Проверим, активен ли эскиз ?.
    If Not TypeOf ThisApplication.ActiveEditObject Is Sketch Then
        MsgBox "Эскиз должен быть активным."
        Exit Sub
    End If

    Dim oSketch As Sketch
    Set oSketch = ThisApplication.ActiveEditObject
    
    ' Получим ссылку на объект TransientGeometry, понадобится для задания точек.
    Dim oTG As TransientGeometry
    Set oTG = ThisApplication.TransientGeometry
    
    ' Начало транзакции.
    Dim oTrans As Transaction
    Set oTrans = ThisApplication.TransactionManager.StartTransaction( _
                                            ThisApplication.ActiveDocument, _
                                            "Keyway Sketch")
    
    ' Создание эскизной геометрии.
    Dim oLine As SketchLine
    Set oLine = oSketch.SketchLines.AddByTwoPoints(oTG.CreatePoint2d(2, 2), _
                                                   oTG.CreatePoint2d(2, -2))
    
    Dim oArc As SketchArc
    Set oArc = oSketch.SketchArcs.AddByCenterStartEndPoint( _
                                            oTG.CreatePoint2d(0, 0), _
                                            oLine.StartSketchPoint, _
                                            oLine.EndSketchPoint)
    
    ' наложение геометрических и размерных зависимостей
    Call oSketch.GeometricConstraints.AddVertical(oLine)
    Call oSketch.DimensionConstraints.AddDiameter(oArc, oTG.CreatePoint2d(-3, 3))
    Call oSketch.DimensionConstraints.AddTwoPointDistance(oLine.StartSketchPoint, _
                                            oArc.CenterSketchPoint, _
                                            kHorizontalDim, _
                                            oTG.CreatePoint2d(0, 3.5))
                                            
    ' Конец транзакции.
    oTrans.End
End Sub


Этот пример весьма прост, но он демонстрирует основы техники применения API для управления транзакциями. Вызов метода StartTransaction менеджера транзакций TransactionManager создает новую транзакцию. Далее пример вызывает другие функции API, а затем транзакция завершается вызовом метода End объекта Transaction. Обратите внимание на два аргумента в вызове StartTransaction. Первый аргумент вводит в заблуждение многих разработчиков, особенно на начальном этапе знакомства с транзакциями. Первым аргументом является документ. Поскольку при создании транзакции от вас требуется указать документ, вы можете предположить, что транзакции ассоциированы с документом. Это не так.

Транзакции формируются на уровне приложения и никак не связаны с какими-либо конкретными документами.

По этой причине первый аргумент смысла не имеет. Тем не менее, он обязателен, и вам придется его указывать. Достаточно указать любой документ.

Вторым аргументом является описание транзакции. Это наименование конечный пользователь увидит в меню Редактирование (Edit), как показано на рисунке.

Transactions_2

Транзакции: вид изнутри

[править]

Прежде, чем далее описывать API для управления транзакциями, позвольте несколько подробнее рассмотреть их работу. Нас будут интересовать нюансы, нередко ускользающие от внимания рядового пользователя. Когда вы выполняете некоторые действия, в Инвенторе генерируются транзакции. Каждое ваше действие помещается в стек транзакций. Его размер по умолчанию равен десяти транзакциям. Пока транзакция находится в стеке, она может быть отменена (Undone) или выполнена снова (Redone). Рассмотрим примеры.

По мере выполнения вами операций транзакции по очереди помещаются в стек. На рисунке ниже показан полностью заполненный стек из десяти транзакций.

Transactions_3


Стек сейчас полон, с очередной вашей операцией, которая порождает транзакцию, самая старая транзакция из стека удаляется. Это означает, что отменить ее уже не удастся.

Transactions_4


Когда вы выполняете отмену действия (Undo), транзакция остается в стеке, но все, что она делала с моделью, оказывается отмененным.

Transactions_5


Отмененные транзакции остаются в стеке, чтобы пользователь смог снова их выполнить — применить к ним операцию «Повторить» (Redo). На следующем рисунке пользователь выполнил подряд три операции отмены — транзакции 9, 10 и 11 отменены. В этот момент все они еще могут быть восстановлены (Redo).

Пусть в этот момент пользователь выполнил действие, которое породило очередную транзакцию. Все отмененные на этот момент транзакции из стека удаляются, и туда помещается новая транзакция.

Transactions_6


Это означает, что удаленные транзакции более не могут быть восстановлены и выполнены снова. В примере справа выполнение новой транзакции 12 приводит к окончательному удалению из стека транзакций 9, 10 и 11.

И последнее, что важно знать и что вызывает наибольшее количество недоразумений, — при закрытии документа стек полностью очищается. Это означает, что после закрытия документа вы лишаетесь возможности отката любых действий, совершенных к этому моменту. Это распространяется как на видимые (visible), так и на скрытые (hidden) документы. Например, если вы откроете документ, назначив аргументу Visible метода Open значение False, чтобы документ стал невидимым после открытия, выполните какие-нибудь действия в этом документе и закроете его, стек откатов окажется очищенным.